﻿namespace Zen.Data.Extensions
{
	using System;
	using System.Collections.Generic;
	using System.Data.Objects;
	using System.Threading;
	using System.Runtime.Remoting.Messaging;
	using System.Diagnostics;

	/// <summary>
	/// <c>ObjectContextScope</c> is a call-stack context wrapper that allows
	/// subsequence instantiations of ObjectContextScope to share a common
	/// Object Context.
	/// </summary>
	/// <typeparam name="T"></typeparam>
	public class ObjectContextScope<T> : IDisposable
		where T : ObjectContext, IObjectContextIdentifier, new()
	{
		#region Private Fields
		private LinkedListNode<ObjectContextScope<T>> _current;
		private Guid _identifier;
		private bool _disposed;
		private bool _isHoldChanges;
		private bool _isRoot;
		private bool _isOwner;
		private T _context;
		#endregion

		#region Public Constructors
		/// <summary>
		/// Initializes a new instance of the <see cref="ObjectContextScope&lt;T&gt;"/> class.
		/// </summary>
		public ObjectContextScope()
			: this(false)
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ObjectContextScope&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="forceNew">
		/// Flag indicating whether to always create a new ObjectContext.
		/// </param>
		public ObjectContextScope(bool forceNew)
		{
			// Create new scope if new is forced, otherwise
			// inherit if one is available
			if (forceNew || Current == null)
			{
				// Create an identifier and context for this scope
				_identifier = Guid.NewGuid();
				_context = new T();
				_isRoot = true;
				_isOwner = true;
			}
			else
			{
				// Reuse last identifier and context for this scope
				ObjectContextScope<T> lastScope = Current;
				_identifier = lastScope.Identifier;
				_context = lastScope.Context;
			}

			// Push this scope onto the top of the stack
			_current = new LinkedListNode<ObjectContextScope<T>>(this);
			ContextStack.AddLast(_current);
			Current = this;

			Debug.WriteLine(string.Format(
				"ObjectContextScope ctor [Ident:{0},ContextId:{1},IsRoot:{2},IsOwner:{3}]",
				_identifier,
				_context,
				_isRoot,
				_isOwner));
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="ObjectContextScope&lt;T&gt;"/> class.
		/// </summary>
		/// <param name="context">The object context to wrap a context scope around.</param>
		/// <param name="isOwner">if set to <c>true</c> the scope will own the context.</param>
		public ObjectContextScope(T context, bool isOwner = true)
		{
			// Create an identifier and cache context for this scope
			_identifier = Guid.NewGuid();
			_context = context;
			_isRoot = true;
			_isOwner = isOwner;

			// Push this scope onto the top of the stack
			_current = new LinkedListNode<ObjectContextScope<T>>(this);
			ContextStack.AddLast(_current);
			Current = this;

			Debug.WriteLine(string.Format(
				"ObjectContextScope ctor [Ident:{0},ContextId:{1},IsRoot:{2},IsOwner:{3}]",
				_identifier,
				_context,
				_isRoot,
				_isOwner));
		}

		/// <summary>
		/// Releases unmanaged resources and performs other cleanup operations before the
		/// <see cref="ObjectContextScope&lt;T&gt;"/> is reclaimed by garbage collection.
		/// </summary>
		~ObjectContextScope()
		{
			Dispose(false);
		}
		#endregion

		#region Public Properties
		/// <summary>
		/// Gets or sets the current.
		/// </summary>
		/// <value>The current.</value>
		public ObjectContextScope<T> Current
		{
			get
			{
				string contextKey = typeof(T).FullName + ":Current";
				return (ObjectContextScope<T>)CallContext.GetData(contextKey);
			}
			private set
			{
				string contextKey = typeof(T).FullName + ":Current";
				CallContext.SetData(contextKey, value);
			}
		}

		private LinkedList<ObjectContextScope<T>> ContextStack
		{
			get
			{
				string contextKey = typeof(T).FullName + ":Stack";
				LinkedList<ObjectContextScope<T>> stack =
					(LinkedList<ObjectContextScope<T>>)CallContext.GetData(contextKey);
				if (stack == null)
				{
					stack = new LinkedList<ObjectContextScope<T>>();
					CallContext.SetData(contextKey, stack);
				}
				return stack;
			}
		}

		/// <summary>
		/// Gets the identifier of the current object space.
		/// </summary>
		/// <remarks>
		/// The identifier of an object space is shared by all 
		/// instances of ObjectContextScope&lt;T&gt; in an inheritance
		/// chain. The identifier is central to comparing different
		/// instance chains of ObjectSpace.
		/// </remarks>
		public Guid Identifier
		{
			get
			{
				return _identifier;
			}
		}

		/// <summary>
		/// Gets the ObjectContext associated with this instance.
		/// </summary>
		public T Context
		{
			get
			{
				if (_disposed)
				{
					throw new ObjectDisposedException(GetType().FullName);
				}
				return _context;
			}
		}

		/// <summary>
		/// Gets a flag indicating whether this instance is a root object.
		/// </summary>
		public bool IsRoot
		{
			get
			{
				return _isRoot;
			}
		}
		#endregion

		#region Private Properties
		/// <summary>
		/// Gets the previous object context scope in this chain.
		/// </summary>
		private ObjectContextScope<T> Previous
		{
			get
			{
				if (_current.Previous != null)
				{
					return _current.Previous.Value;
				}
				else
				{
					return null;
				}
			}
		}

		/// <summary>
		/// Gets the next object context scope in this chain.
		/// </summary>
		/// <value>The next.</value>
		private ObjectContextScope<T> Next
		{
			get
			{
				if (_current.Next != null)
				{
					return _current.Next.Value;
				}
				else
				{
					return null;
				}
			}
		}

		/// <summary>
		/// Gets a flag indicating whether any changes made
		/// by ObjectSpaces that in herit from this instance
		/// chain are held and not persisted until the hold is
		/// released.
		/// </summary>
		private bool IsHoldingPersistance
		{
			get
			{
				bool holdChanges = _isHoldChanges;
				if (!holdChanges && !IsRoot && Previous != null)
				{
					holdChanges |= Previous.IsHoldingPersistance;
				}

				return holdChanges;
			}
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// Disables any changes made in child spaces from
		/// being persisted in a call to PersistChanges().
		/// </summary>
		public void LockPersistance()
		{
			_isHoldChanges = true;
		}

		/// <summary>
		/// Allows any changes made in child spaces
		/// to be persieted in a call to PersistChanges().
		/// </summary>
		public void UnlockPersistance()
		{
			_isHoldChanges = false;
		}

		/// <summary>
		/// Persists any changes made within this object
		/// space to the database.
		/// </summary>
		/// <remarks>
		/// This method will call Context.SaveChanges() unless
		/// a lock has been taken higher up the inheritance
		/// chain.
		/// </remarks>
		public void PersistChanges()
		{
			bool doSave = true;

			if (!IsRoot)
			{
				ObjectContextScope<T> parent = Previous;
				if (parent != null && parent.IsHoldingPersistance)
				{
					doSave = false;
				}
			}

			if (doSave)
			{
				Context.SaveChanges();
			}
		}

		/// <summary>
		/// Returns a hash code for this instance.
		/// </summary>
		/// <returns>
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
		/// </returns>
		public override int GetHashCode()
		{
			return _identifier.GetHashCode();
		}

		/// <summary>
		/// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
		/// </summary>
		/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
		/// <returns>
		/// 	<c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
		/// </returns>
		public override bool Equals(object obj)
		{
			if (obj is ObjectContextScope<T>)
			{
				ObjectContextScope<T> space = (ObjectContextScope<T>)obj;
				return space.Identifier == this.Identifier;
			}
			else
			{
				return false;
			}
		}

		/// <summary>
		/// Returns a <see cref="System.String"/> that represents this instance.
		/// </summary>
		/// <returns>
		/// A <see cref="System.String"/> that represents this instance.
		/// </returns>
		public override string ToString()
		{
			return "ObjectContextScope<" + typeof(T).FullName + ">::" + _identifier.ToString();
		}

		/// <summary>
		/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Disposes of the ObjectSpace, including the ObjectContext if this 
		/// is the root space.
		/// </summary>
		/// <param name="disposing">Flag indicating if this is a true disposal.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing)
			{
				_disposed = true;

				Debug.WriteLine(string.Format(
					"ObjectContextScope dtor [Ident:{0},Context:{1},IsRoot:{2},IsOwner:{3}]",
					_identifier,
					_context,
					_isRoot,
					_isOwner));

				if (Current == this)
				{
					if (Previous != null)
					{
						Current = Previous;
					}
					else
					{
						Current = Next;
					}
				}

				if (IsRoot && _context != null)
				{
					if (Next == null || Next.IsRoot)
					{
						if (_isOwner)
						{
							_context.Dispose();
						}
					}
					else
					{
						Next._isRoot = true;
					}
				}
				_context = null;

				ContextStack.Remove(_current);
				_current = null;
			}
		}
		#endregion
	}
}
